package us.versus.them.triangle_mountain;

import opengl.GL;
import opengl.GLU;
import opengl.GLFW;

class App {
	static var f = 2;

	var lod   : Int;
	var close : Bool;
	var auto  : Bool;
	var per_vertex_nomals : Bool;

	var camera : Camera;

	var triangles  : Array< Triangle >;
	var pt_normals : Hash< Pt3 >;

	var listId : Int;

	var frameCount : Int;
	var startTime : Float;

	public function init() {

		/*
				p1              p2
				  .------------. 
				   \` .         \
				    \   ` .      \
				     \     ` .    \
				      \        ` . \
				    p4 .------------. p3

		*/
		var p1 = new Pt3( -f, 0, -f );
		var p2 = new Pt3(  f, 0, -f );
		var p3 = new Pt3(  f, 0,  f );
		var p4 = new Pt3( -f, 0,  f );

		var tmp = new Array< Triangle >();
		tmp.push( new Triangle( p1, p2, p3 ) );
		tmp.push( new Triangle( p1, p3, p4 ) );

		var midPointBuddy = new MidPointBuddy( f );

		var max = 1.0;
		for ( i in 0 ... lod ) {
			var nu_tmp = new Array< Triangle >();
			for ( triangle in tmp ) {
				for ( nu in triangle.split( max, midPointBuddy.getMidPoint ) ) {
					nu_tmp.push( nu );
				}
			}
			tmp = nu_tmp;
			max *= 0.55;
		}

		this.pt_normals = new Hash< Pt3 >();
		for ( triangle in tmp ) {
			triangle.calculateNormal( this.pt_normals );
		}
		for ( pt in this.pt_normals ) {
			pt.average(); // not good enuff...
		}

		trace( 'lod=' + this.lod + ' -> ' + tmp.length + ' triangles' );
		this.triangles = tmp;
	}

	public function drawTriangles() {
		GL.color3( 0, 1, 0 );
		GL.begin( GL.TRIANGLES );
		for ( triangle in this.triangles ) {
			triangle.draw( if( per_vertex_nomals ) this.pt_normals else null );
		}
		GL.end();
	}
	
	public function display() {
		GL.clear( GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT );
		GL.loadIdentity();

		camera.look();
		GL.scale( 1., 1., 1. );

		if ( auto ) {
			camera.rotation.x = 14;
			camera.rotation.y += 2;
			//z += 3;
		}

		if ( -1 == this.listId ) {
			this.drawTriangles();
			this.drawSea();
		} else {
			GL.callList( this.listId );
		}
		
		GL.flush();
		GLFW.swapBuffers();

		this.frameCount++;
		if ( 1000 == this.frameCount ) {
			this.fps();
		}

	}

	public function fps() {
		var now = ( Date.now() ).getTime() / 1000.0;
		var tno = this.triangles.length * this.frameCount;
		var diff = now - this.startTime;
		trace( 
			this.frameCount + '/' + diff + '=' + ( this.frameCount / diff ) + " fps"
			+ ' / ' +
			tno + '/' + diff + '=' + ( tno / diff ) + " tps"
		);
		this.resetTimer();
	}

	public function createList() {
		var n =  GL.genLists( 1 );
		GL.newList( n, GL.COMPILE_AND_EXECUTE );
		this.drawTriangles();
		this.drawSea();
		GL.endList();
		this.listId = n;
		trace( 'created list ' + n );
		this.resetTimer();
	}

	public function destroyList() {
		var n = this.listId;
		this.listId = -1;
		GL.deleteLists( n, 1 );
		trace( 'destroyed list ' + n );
		this.resetTimer();
	}

	public function resetTimer() {
		this.frameCount = 0;
		this.startTime = ( Date.now() ).getTime() / 1000.0;
	}

	public function drawSea() {
		//GL.enable( GL.BLEND );
		//GL.disable( GL.DEPTH_TEST );

		GL.begin( GL.QUADS );

		Color.BLUE.color();
		GL.color4( 0, 0, 1, 0.5 );
		GL.color3( 0, 0, 1 );
		GL.normal3( 0, 1, 0 );
		GL.vertex3( -f * 2, 0, -f * 2 );

		Color.BLUE.color();
		GL.color4( 0, 0, 1, 0.5 );
		GL.normal3( 0, 1, 0 );
		GL.vertex3(  f * 2, 0, -f * 2 );

		Color.BLUE.color();
		GL.color4( 0, 0, 1, 0.5 );
		GL.normal3( 0, 1, 0 );
		GL.vertex3(  f * 2, 0,  f * 2 );

		Color.BLUE.color();
		GL.color4( 0, 0, 1, 0.5 );
		GL.normal3( 0, 1, 0 );
		GL.vertex3( -f * 2, 0,  f * 2 );

		GL.end();
		GL.disable( GL.BLEND );
		GL.enable( GL.DEPTH_TEST );
	}

	public function charFunction( a:Int, b:Int ) {
		if ( 1 == b ) {
			var inc = 1;
			switch( a ) {
				case  32 /*   */: 
				case  76 /* L */: this.lod--; init();
				case  82 /* R */: this.reset(); init();
				case  88 /* X */: auto = false; camera.rotation.x -= inc; 
				case  89 /* Y */: auto = false; camera.rotation.y -= inc;
				case  90 /* Z */: auto = false; camera.rotation.z -= inc;
				case  97 /* a */: auto = !auto;
				case 108 /* l */: this.lod++; init();
				case 112 /* p */: this.per_vertex_nomals = !this.per_vertex_nomals; 
				case 120 /* x */: auto = false; camera.rotation.x += inc; 
				case 121 /* y */: auto = false; camera.rotation.y += inc;
				case 122 /* z */: auto = false; camera.rotation.z += inc;
				case 113 /* q */: close = true;
				case 114 /* r */: this.init();
				case 115 /* s */: if ( -1 == this.listId ) this.createList(); else this.destroyList();
				default:
					trace("char: "+a+", "+b );
			}
			trace( camera.rotation.print() + '/' + camera.position.print() );
		}
	}

	public function keyFunction( a:Int, b:Int ) {
		if ( 1 == b ) {
			switch( a ) {
				case 283 /* up   arrow */: camera.position.z--;
				case 284 /* down arrow */: camera.position.z++;
				case 285 /* left arrow */: camera.position.x--;
				case 286 /* rite arrow */: camera.position.x++;
				default:
					trace("key: "+a+", "+b );
			}
		}
	}

	public function windowCloseFunction() {
		trace("window close" );
		close = true;
		return 1;
	}

	public function reset() {
		this.listId = -1;

		this.lod = 5;
		this.per_vertex_nomals = !true;
		this.close = false;
		this.auto = true;

		this.camera = new Camera();
		this.camera.rotation.x = 14;

		this.resetTimer();
	}

	public function new() {
		this.reset();
		 		
		GLFW.openWindow( 800, 600, 8,8,8, 8,8,0, GLFW.WINDOW );
		GLFW.setCharFunction( charFunction );
		GLFW.setKeyFunction( keyFunction );
		GLFW.setWindowCloseFunction( windowCloseFunction );
		
		GLFW.setWindowSizeFunction( function( w:Int, h:Int ) {
			trace("window resize: "+w+"x"+h );
		});
		GLFW.setWindowRefreshFunction( function() {
			trace("window refresh" );
		});
		GLFW.setMouseButtonFunction( function( a:Int, b:Int ) {
			//trace("mouseButton: "+a+", "+b );
		});
		GLFW.setMousePosFunction( function( a:Int, b:Int ) {
			//trace("mousePos: "+a+", "+b );
		});
		GLFW.setMouseWheelFunction( function( a:Int ) {
			//trace("mouseWheel: "+a );
		});

		this.init();
		this.setupGL();
		this.loop();
	}

	public function setupGL() {

		GL.color4( 1.0, 1.0, 1.0, 1 );
		GL.blendFunc( GL.SRC_ALPHA, GL.ONE );

		GL.clearColor( 0, 0, 0, 0 );
		GL.shadeModel( GL.FLAT );

		// lights...
		GL.shadeModel( GL.FRONT_AND_BACK );
		GL.enable( GL.LIGHTING );
		GL.enable( GL.COLOR_MATERIAL );
		GL.colorMaterial( GL.FRONT_AND_BACK, GL.AMBIENT_AND_DIFFUSE );
		GL.enable( GL.LIGHT0 );
		
		GL.matrixMode( GL.PROJECTION );
		GL.loadIdentity();
		GL.frustum( -1., 1., -1., 1., 1.5, 20. );
		GL.matrixMode( GL.MODELVIEW );
		
		GL.enable( GL.DEPTH_TEST );
	}

	public function loop() {
		while(!close) {
			GLFW.pollEvents();
			display();
			neko.Sys.sleep(1./50);
		}
		GLFW.terminate();
	}
}
